package com.free.beanutils;

import org.apache.commons.beanutils.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 功能概述: 
 * </p>
 * <p>
 * 功能详述: 
 * </p>
 */
public class BeanUtilsBean extends org.apache.commons.beanutils.BeanUtilsBean
{

    private static final String PROJECT_CLS_BASE_NM = "com.free";
    
    BeanUtilsBean()
    {
        super();
    }
    
    /**
     * <p>Constructs an instance using given conversion instances
     * and new {@link PropertyUtilsBean} instance.</p>
     *
     * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
     * to perform conversions from one object to another
     *
     * @since 1.8.0
     */
    public BeanUtilsBean(final ConvertUtilsBean convertUtilsBean) {
        this(convertUtilsBean, new PropertyUtilsBean());
    }
    
    public BeanUtilsBean(final ConvertUtilsBean convertUtilsBean, PropertyUtilsBean propertyUtilsBean)
    {
    	super(convertUtilsBean,propertyUtilsBean);
    }
    
    /*========================================================================*
     *                         Public Methods (公有方法)                                                                   
     *========================================================================*/

   
    /**
     * Gets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @return The (pseudo-singleton) BeanUtils bean instance
     */
    public static BeanUtilsBean getInstance()
    {
        return BEANS_BY_CLASSLOADER.get();
    }


    /**
     * <p>Copy property values from the origin bean to the destination bean
     * for all cases where the property names are the same.  For each
     * property, a conversion is attempted as necessary.  All combinations of
     * standard JavaBeans and DynaBeans as origin and destination are
     * supported.  Properties that exist in the origin bean, but do not exist
     * in the destination bean (or are read-only in the destination bean) are
     * silently ignored.</p>
     *
     * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
     * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
     * the corresponding property values that will be converted (if necessary)
     * and set in the destination bean. <strong>Note</strong> that this method
     * is intended to perform a "shallow copy" of the properties and so complex
     * properties (for example, nested ones) will not be copied.</p>
     *
     * <p>This method differs from <code>populate()</code>, which
     * was primarily designed for populating JavaBeans from the map of request
     * parameters retrieved on an HTTP request, is that no scalar->indexed
     * or indexed->scalar manipulations are performed.  If the origin property
     * is indexed, the destination property must be also.</p>
     *
     * <p>If you know that no type conversions are required, the
     * <code>copyProperties()</code> method in {@link PropertyUtils} will
     * execute faster than this method.</p>
     *
     * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
     * have getter and setter methods for the underlying array or Map are not
     * copied by this method.</p>
     *
     * @param dest Destination bean whose properties are modified
     * @param orig Origin bean whose properties are retrieved
     *
     * @throws IllegalAccessException if the caller does not have
     *  access to the property accessor method
     * @throws IllegalArgumentException if the <code>dest</code> or
     *  <code>orig</code> argument is null or if the <code>dest</code>
     *  property type is different from the source type and the relevant
     *  converter has not been registered.
     * @throws InvocationTargetException if the property accessor method
     *  throws an exception
     */
    public void copyProperties(final Object dest, final Object orig, final String[] ingores) throws IllegalAccessException, InvocationTargetException
    {

        // Validate existence of the specified beans
        if (dest == null)
        {
            throw new IllegalArgumentException("No destination bean specified");
        }
        if (orig == null)
        {
            throw new IllegalArgumentException("No origin bean specified");
        }

        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean)
        {
            final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (DynaProperty origDescriptor : origDescriptors)
            {
                final String name = origDescriptor.getName();
                // Need to check isReadable() for WrapDynaBean
                // (see Jira issue# BEANUTILS-61)
                if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name))
                {
                    final Object value = ((DynaBean) orig).get(name);
                    if(isRequireProperty(ingores, name))
                    {
                        copyProperty(dest, name, value);
                    }
                }
            }
        }
        else if (orig instanceof Map)
        {
            @SuppressWarnings("unchecked")
            final// Map properties are always of type <String, Object>
            Map<String, Object> propMap = (Map<String, Object>) orig;
            for (final Map.Entry<String, Object> entry : propMap.entrySet())
            {
                final String name = entry.getKey();
                if (getPropertyUtils().isWriteable(dest, name))
                {
                    if(isRequireProperty(ingores, name))
                    {
                        copyProperty(dest, name, entry.getValue());
                    }
                }
            }
        }
        else
        /* if (orig is a standard JavaBean) */{
            final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
            for (PropertyDescriptor origDescriptor : origDescriptors)
            {
                final String name = origDescriptor.getName();
                if ("class".equals(name))
                {
                    continue; // No point in trying to set an object's class
                }
                if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name))
                {
                    try
                    {
                        final Object value = getPropertyUtils().getSimpleProperty(orig, name);
                        if(isRequireProperty(ingores, name))
                        {
                            copyProperty(dest, name, value);
                        }
                    }
                    catch (final NoSuchMethodException e)
                    {
                        // Should not happen
                    }
                }
            }
        }
    }
    
    /**
     * <p>Copy property values from the origin bean to the destination bean
     * for all cases where the property names are the same.  For each
     * property, a conversion is attempted as necessary.  All combinations of
     * standard JavaBeans and DynaBeans as origin and destination are
     * supported.  Properties that exist in the origin bean, but do not exist
     * in the destination bean (or are read-only in the destination bean) are
     * silently ignored.</p>
     *
     * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
     * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
     * the corresponding property values that will be converted (if necessary)
     * and set in the destination bean. <strong>Note</strong> that this method
     * is intended to perform a "shallow copy" of the properties and so complex
     * properties (for example, nested ones) will not be copied.</p>
     *
     * <p>This method differs from <code>populate()</code>, which
     * was primarily designed for populating JavaBeans from the map of request
     * parameters retrieved on an HTTP request, is that no scalar->indexed
     * or indexed->scalar manipulations are performed.  If the origin property
     * is indexed, the destination property must be also.</p>
     *
     * <p>If you know that no type conversions are required, the
     * <code>copyProperties()</code> method in {@link PropertyUtils} will
     * execute faster than this method.</p>
     *
     * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
     * have getter and setter methods for the underlying array or Map are not
     * copied by this method.</p>
     *
     * @param dest Destination bean whose properties are modified
     * @param orig Origin bean whose properties are retrieved
     *
     * @throws IllegalAccessException if the caller does not have
     *  access to the property accessor method
     * @throws IllegalArgumentException if the <code>dest</code> or
     *  <code>orig</code> argument is null or if the <code>dest</code>
     *  property type is different from the source type and the relevant
     *  converter has not been registered.
     * @throws InvocationTargetException if the property accessor method
     *  throws an exception
     */
    public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {

        // Validate existence of the specified beans
        if (dest == null) {
            throw new IllegalArgumentException
                    ("No destination bean specified");
        }
        if (orig == null) {
            throw new IllegalArgumentException("No origin bean specified");
        }
       
        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean) {
            final DynaProperty[] origDescriptors =
                ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (DynaProperty origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                // Need to check isReadable() for WrapDynaBean
                // (see Jira issue# BEANUTILS-61)
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    final Object value = ((DynaBean) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } else if (orig instanceof Map) {
            @SuppressWarnings("unchecked")
            final
            // Map properties are always of type <String, Object>
            Map<String, Object> propMap = (Map<String, Object>) orig;
            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
                final String name = entry.getKey();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    copyProperty(dest, name, entry.getValue());
                }
            }
        } else /* if (orig is a standard JavaBean) */ {
            final PropertyDescriptor[] origDescriptors =
                getPropertyUtils().getPropertyDescriptors(orig);
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                if ("class".equals(name)) {
                    continue; // No point in trying to set an object's class
                }
                if(getField(dest.getClass(),BeanUtilsHelper.instance().transferPropertyName(name)) == null){
                    continue;
                }
                if(isSpecial(origDescriptor,orig,dest)){
                    continue;
                }
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, BeanUtilsHelper.instance().transferPropertyName(name))) {
                    try {
                        
                        final Object value =
                            getPropertyUtils().getSimpleProperty(orig, name);
                        copyProperty(dest, BeanUtilsHelper.instance().transferPropertyName(name), value);
                    } catch (final NoSuchMethodException e) {
                        // Should not happen
                    }
                }
            }
        }
    }
 

    
    private boolean isSpecial(PropertyDescriptor origDescriptor, Object orig, Object dest ) 
    {
        boolean flg = false;
        Class<?> cls = origDescriptor.getPropertyType();
        //枚举直接操作
        if(cls.isEnum()){
            return convertEnumToValue(origDescriptor,orig,dest);
        }
        if(cls.isAssignableFrom(List.class)){
            return convertList(origDescriptor,orig,dest);
        }
        // 复杂类型的(jpt)class复制时
        if(BeanUtilsHelper.instance().isInPackages(cls.getName())){
            try
            {
                String name = origDescriptor.getName();
                final Object orgVal =
                        getPropertyUtils().getSimpleProperty(orig, name);
                if(orgVal == null){
                    getPropertyUtils().setSimpleProperty(dest, name, orgVal);
                    return true;
                }
                Object destVal = getPropertyUtils().getSimpleProperty(dest, name);
                if(destVal == null){
                    PropertyDescriptor destProDesp =  getPropertyUtils().getPropertyDescriptor(dest, name);
                    destVal = destProDesp.getPropertyType().newInstance();
                }
                copyProperties(destVal,orgVal);
                getPropertyUtils().setSimpleProperty(dest, name, destVal);
                flg = true;
            }catch(Throwable t){
                flg = false;
                throw new IllegalArgumentException("复制失败",t);
            }
        }
        return flg;
    }
    
    /*========================================================================*
     *                         Private Methods (私有方法)                                                                   
     *========================================================================*/
    
    private boolean convertEnumToValue(PropertyDescriptor origDescriptor, Object orig, Object dest)
    {
        try
        {
            String name = origDescriptor.getName();
            final Object orgVal =
                    getPropertyUtils().getSimpleProperty(orig, name);
            if(orgVal == null){
                getPropertyUtils().setSimpleProperty(dest, name, orgVal);
                return true;
            }
            Method  method = origDescriptor.getPropertyType().getDeclaredMethod("getKey");
            Object destVal = null;
            if(method != null){
                destVal = method.invoke(orgVal);
            }
            getPropertyUtils().setSimpleProperty(dest, name, destVal);
            return true;
        }
        catch(Throwable t){
            throw new IllegalArgumentException("复制失败",t);
        }
    }
    
    @SuppressWarnings({"rawtypes", "unchecked"})
    private boolean convertList(PropertyDescriptor origDescriptor, Object orig, Object dest)
    {
        try{
            String name = origDescriptor.getName();
            
            Class<?> destParameterType = getListParameterType(dest,getField(dest.getClass(),name));
            
            if( destParameterType == null){
                return true;
            }
            
            final List orgVal = (List)getPropertyUtils().getSimpleProperty(orig, name);
            if(orgVal == null){
                getPropertyUtils().setSimpleProperty(dest, name, null);
                return true;
            }
            List destVal = new ArrayList();
            
            for(Object obj:orgVal){
                Object destTmp = null;
                if(isLangType(obj.getClass())){
                    destTmp = obj;
                    
                }else{
                    destTmp = destParameterType.newInstance();
                    copyProperties(destTmp,obj);
                }
                destVal.add(destTmp);
            }
            
            getPropertyUtils().setSimpleProperty(dest, name,destVal);
            
            return true;
            
        }catch(Throwable t){
            throw new IllegalArgumentException("复制失败",t);
        }
    }
    
    
    private boolean isLangType(Class<?> objType){
        if(objType.equals(Integer.class) || objType.equals(String.class) || objType.equals(Long.class) || objType.equals(Boolean.class)){
            return true;
        }
        return false;
        
    }
    
    private Field getField(Class<?> clazzType, String fieldNm)
    {
        if(clazzType == null){
            return null;
        }
        try{
            return clazzType.getDeclaredField(fieldNm);
        }catch(NoSuchFieldException e){
            if(clazzType.getSuperclass()!=null && !clazzType.getSuperclass().equals(Object.class)){
                return getField(clazzType.getSuperclass(),fieldNm);
            }
            return null;
        }
    }
    
    private Class<?> getListParameterType(Object dest,Field field) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
    {
        if(dest.getClass().getName().indexOf("PageResponseVo")>=0) //PageResponseVo需要这么做
        {
            Method method = dest.getClass().getMethod("getTClazz");
            Class<?> parameterClass = (Class<?>)method.invoke(dest);
            return parameterClass;
        }
        if(field == null){
            return null;
        }
        Type  fc =  field.getGenericType();
        if(fc instanceof ParameterizedType){
            ParameterizedType pt = (ParameterizedType) fc;
            Class<?> genericClazz = (Class<?>)pt.getActualTypeArguments()[0];
            return genericClazz;
        }
        return (Class<?>)fc;
    }
    
    
    private boolean isRequireProperty(String[] ingores, String name)
    {
        if(ingores != null && ingores.length > 0)
        {
          if(Arrays.asList(ingores).contains(name))
          {
              return false;
          }
        }
        return true;
    }
    
    
    /**
     * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
     */
    private static final ContextClassLoaderLocal<BeanUtilsBean> BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>()
    {
        // Creates the default instance used when the context classloader is unavailable
        @Override
        protected BeanUtilsBean initialValue()
        {
            return new BeanUtilsBean();
        }
    };
}
